﻿using System.Collections.Generic;
using EmbeddedSharp.Core;
using EmbeddedSharp.Core.Function.Arithmetics;

namespace Zove.Runtime.Core.InferencingStrategy {
    public class SearchResult {
        public SearchResult() {
            Elements = new List<string>();
        }

        public RuntimeEquation Equation { get; set; }

        public List<string> Elements { get; set; }
    }

    public class EquationGroup {
        public EquationGroup() {
            Elements = new List<string>();
            Equations = new List<RuntimeEquation>();
        }

        public EquationGroup(List<RuntimeEquation> eg)
            : this() {
            Equations = eg;
        }

        public List<RuntimeEquation> Equations { get; set; }

        public List<string> Elements { get; set; }

        public override int GetHashCode() {
            return base.GetHashCode();
        }

        public override bool Equals(object obj) {
            if (!obj.GetType().Equals(typeof(EquationGroup))) {
                return false;
            }

            var eg = (EquationGroup)obj;

            foreach (RuntimeEquation e in eg.Equations) {
                if (!Equations.Contains(e)) {
                    return false;
                }
            }

            return true;
        }
    }

    /// <summary>
    /// Search result comparer
    /// </summary>
    public class SearchResultComparer : IComparer<SearchResult> {
        #region IComparer<SearchResult> Members

        public int Compare(SearchResult x, SearchResult y) {
            return x.Elements.Count.CompareTo(y.Elements.Count);
        }

        #endregion
    }

    /// <summary>
    /// Nonlinear equation set inferencing strategy
    /// </summary>
    public class NonlinearInferencingStrategy : AbstractInferencingStrategy {

        private readonly List<SearchResult> results = new List<SearchResult>();

        private ZoveObject sln;

        /// <summary>
        /// Evaluates
        /// </summary>
        /// <param name="eg"></param>
        /// <returns></returns>
        public double[] Evaluate(EquationGroup eg) {
            var s = new List<string>();
            var rpl = new Dictionary<string, string>();

            foreach (RuntimeEquation e in eg.Equations) {
                var node = new SubNode();
                node.Left = e.Left;
                node.Right = e.Right;

                for (int index = 0; index < e.Factors.Count; index++) {
                    string x = "x" + index;
                    rpl[e.Factors[index].Name] = x;
                }

                var ef
                    = new CSharpExpressionTranslator();

                string m = ef.Translate(node);
                m = m + "=0";
                s.Add(m);

                Utils.Debugging("expr: " + m);
            }

            // NonLinear.SetSystem(s.ToArray());
            var r = new double[s.Count]; //NonLinear.NewtonRapson();

            for (int index = 0; index < eg.Elements.Count; index++) {
                Utils.Debugging("e: " + eg.Elements[index]);
                //this.sln.Space[eg.Elements[index]] = r[index];
                RuntimeFactor rf = sln.Model.Factors[eg.Elements[index]];
                rf.Value = r[index];
                Utils.Debugging(eg.Elements[index] + " = " + r[index]);
            }

            return r;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="zoveObject"></param>
        public override void Infer(ZoveObject zoveObject) {
            sln = zoveObject;
            Utils.Debugging("Mat:");
            Utils.Debugging(zoveObject.Model.ToString());
            foreach (RuntimeEquation r in zoveObject.Model.Equations) {
                var result = new SearchResult();
                result.Equation = r;
                results.Add(result);

                foreach (RuntimeFactor e in r.Factors) {
                    if (e.Status != RuntimeFactorStatus.Unknown) {
                        result.Elements.Add(e.Name);
                    }
                }
            }

            results.Sort(new SearchResultComparer());

            var ses = new List<EquationGroup>();

            foreach (SearchResult r in results) {
                var eg = new EquationGroup();
                eg.Equations.Add(r.Equation);
                eg.Elements.AddRange(r.Elements);

                foreach (SearchResult v in results) {
                    if (r == v) {
                        continue;
                    }

                    if (r.Elements.Count == v.Elements.Count) {
                        bool done = true;
                        foreach (string m in r.Elements) {
                            if (!v.Elements.Contains(m)) {
                                done = false;
                                break;
                            }
                        }
                        if (done) {
                            eg.Equations.Add(v.Equation);
                            if (eg.Elements.Count == r.Elements.Count) {
                                ses.Add(eg);
                            }
                        }
                    }
                }
            }

            for (int i = 0; i < ses.Count; i++) {
                for (int j = 0; j < ses.Count; j++) {
                    if (i == j) {
                        continue;
                    }

                    if (ses[i].Equals(ses[j])) {
                        ses.RemoveAt(j);
                    }
                }
            }

            ShowSet(ses);

            foreach (EquationGroup eg in ses) {
                Evaluate(eg);
            }
        }

        /// <summary>
        /// Show the equation set
        /// </summary>
        /// <param name="egs">the equation set</param>
        public static void ShowSet(List<EquationGroup> egs) {
            Utils.Debugging("======================");
            foreach (EquationGroup eg in egs) {
                foreach (RuntimeEquation e in eg.Equations) {
                    Utils.Debugging(e.Equation);
                }
                Utils.Debugging("======================");
            }
        }
    }
}